#include "qq_server.h"

#define USERDATA_MAXLEN 1024

//用户链表更新(登入、登出)
sem_t usersUpdate;
sem_t usersInfoUpdate;
//当前在线用户
user_node *cur_users;
//所有的用户
user_node *all_users;

friend frienddata[1024];//关系
int friendnum = 0;

//获取服务端的所有好友数据
void get_friendinfo(void)
{
    int fd = open("friend.txt",O_RDONLY);
    if(fd < 0)
    {
        perror("open fail");
        return ;
    }
    friend info;
    while(read(fd,&info,sizeof(friend)) != 0)
    {
        frienddata[friendnum] = info;
        friendnum++;
    }
    close(fd);
}

//保存好友(更新文件)
void save_friendinfo(void)
{
    int fd = open("friend.txt",O_WRONLY | O_TRUNC | O_CREAT);
    if(fd < 0)
    {
        perror("open fail");
        return ;
    }
    for(int i=0;i<friendnum;i++)
    {
        printf("%d %d %s\n",frienddata[i].cur_id,frienddata[i].id,frienddata[i].username);
        write(fd,&frienddata[i],sizeof(friend));
    }
    close(fd);
}

//编辑某个用户的好友数据
void edit_client_friend(int client_id)
{
    printf("clientid:%d\n",client_id);
    user_node *aclient = find_user_by_id(all_users,client_id);
    printf("%d:%s\n",aclient->user.id,aclient->user.username);
    int num = 0;
    for(int i=0;i<friendnum;i++)
    {
        if(frienddata[i].cur_id == client_id)
        {
            char friendbuf[64];
            sprintf(friendbuf,"-1:%d:%s:0:0",frienddata[i].id,frienddata[i].username);
            printf("friendbuf:%s\n",friendbuf);
            write(aclient->user.connfd,friendbuf,strlen(friendbuf));
            num++;
        }
    }
    printf("ID为%d的好友数量为:%d\n",client_id,num);
    if(num == 0)
    {
        char msg[32];
        sprintf(msg,"-1:0:0:0:0");
        write(aclient->user.connfd,msg,strlen(msg));
    }

}

//验证账号和密码
bool verify(unsigned int id,char *password)
{
    //遍历
    user_node *tmp =all_users->next;
    for( ; tmp!=all_users ; tmp = tmp->next)
    {
        if((tmp->user.id == id) && (strcmp(tmp->user.password,password) == 0))
        {
            return true;
        }
    }
    return false;
}

//私聊消息
void privateTalk(user_node *sender,int type,int receiverID,const char *msg,int size,const char*src_msg)
{
    user_node *p;
    // printf("type:%d\n",type);
    // printf("msg:%s\n",msg);
    // printf("receicerID:%d\n",receiverID);
    // printf("src_msg:%s\n",src_msg);
    char buf[2048];
    //再次拼接: type:sendid:sendname:recvid:msg:size
    sprintf(buf,"%d:%d:%s:%d:%s:%d",type,sender->user.id,sender->user.username,receiverID,msg,size);
    printf("buf=%s\n",buf);
    for( p = cur_users->next ; p != cur_users ; p = p->next)
    {
        printf("p->user.id = %u\n",p->user.id);
        if((int)p->user.id == receiverID && p->user.id != sender->user.id)
        {
            printf("p->user.connfd = %d\n",p->user.connfd);
            if(type == 0) //普通消息
            {
                write(p->user.connfd,buf,strlen(buf));
                return;
            }
            else if(type == 1) //文件信息
            {
                if(size >0)
                {
                    //再次拼接:内容
                    char newbuf[4096];
                    sprintf(newbuf,"%s:%s",buf,src_msg);
                    write(p->user.connfd,newbuf,strlen(newbuf));
                    return;
                }else{
                    write(p->user.connfd,buf,strlen(buf));
                    return;
                }
            }
            else if(type == 2)
            {
                write(p->user.connfd,buf,strlen(buf));
                return;
            }
        }
    }
    printf("找不到信息接收者，通知发送方接收者可能已下线\n");
    // 找不到信息接收者，通知发送方接收者可能已下线
    send(sender->user.connfd,"x",1,MSG_OOB);
}

//线程0:动态更新所有用户信息
void *updatausers(void *arg)
{
    while(1)
    {
        all_users = init_list();
        file_load_data(all_users,"user.txt");
        sem_wait(&usersInfoUpdate);
        file_save_data(all_users,"user.txt");
        destory(all_users);
    }
}

// 线程1:显示当前在线用户
void *show_users(void *arg)
{
    while(1)
    {
        show_list(cur_users);

        //p操作，释放资源(刚开始无资源，无法释放)
        sem_wait(&usersUpdate);
        printf("\n");
    }
}

//线程2:处理用户登入或登出
void *login_logout(void *arg)
{
    // pthread_detach(pthread_self());
    // user_node *client = (user_node*)arg;
    struct sockaddr_in clientAddr = {0};
    socklen_t len = sizeof(clientAddr);
    /**
     * char logmsg[5];
     * int id;
     * char username[20];
     * char password[20];
    */
    char re_msg[32];
    log_info loginfo;
    int n;
    while(1)
    {
        bzero(&loginfo,sizeof(log_info));
        recvfrom(logfd,&loginfo,sizeof(log_info),0,(struct sockaddr *)&clientAddr,&len);

        printf("logmsg:%s\n",loginfo.logmsg);
        //登入请求
        if( strcmp(loginfo.logmsg,"in") == 0)
        {
            if(find_user_by_id(cur_users,loginfo.id) != NULL)
            {
                //账户已在线
                strcpy(re_msg,"fail1");
                sendto(logfd, re_msg, strlen(re_msg), 0,
                (struct sockaddr *)&clientAddr, sizeof(clientAddr));
                continue;
            }
            //检验账号和密码是否正确
            if(verify(loginfo.id,loginfo.password))
            {
                user_node *usernode = find_user_by_id(all_users,loginfo.id);
                char username[16];
                strcpy(username,usernode->user.username);
                //正确：返回一个数据(登入成功) **尝试带外数据
                sprintf(re_msg,"success:%s",username);
                sendto(logfd, re_msg, strlen(re_msg), 0,
                (struct sockaddr *)&clientAddr, sizeof(clientAddr));
                //添加进当前在线用户中
                usernode->user.addr = clientAddr;//更新最近的ip地址
                struct userinfo new_info = usernode->user;
                user_node *new = new_node(&new_info);
                printf("用户%s(%u)[%s:%hu]登入\n", new_info.username,new_info.id,inet_ntoa(clientAddr.sin_addr),
                                         ntohs(clientAddr.sin_port));
                add_user(cur_users,new);
            }
            else{
                //错误
                strcpy(re_msg,"fail");
                sendto(logfd, re_msg, strlen(re_msg), 0,
                (struct sockaddr *)&clientAddr, sizeof(clientAddr));
                continue;
            }
        }
        //登出请求
        else if(strcmp(loginfo.logmsg,"out") == 0)
        {
            user_node *usernode = find_user_by_id(cur_users,loginfo.id);
            if(usernode != NULL)
            {
                printf("用户%s(%u)[%s:%hu]登出\n", usernode->user.username,usernode->user.id,inet_ntoa(clientAddr.sin_addr),
                                         ntohs(clientAddr.sin_port));
                //当前在线用户中删除该node
                del_user(usernode);
                
            }
        }
        //注册请求
        else if(strcmp(loginfo.logmsg,"regist") == 0 )
        {
            printf("regist\n");
            //账户是否存在
            if(find_user_by_id(all_users,loginfo.id) != NULL)
            {
                //账户存在
                strcpy(re_msg,"regist fail");
                sendto(logfd, re_msg, strlen(re_msg), 0,
                (struct sockaddr *)&clientAddr, sizeof(clientAddr));
                continue;
            }
            else{
                //账户不存在(多次密码在客户端验证)
                struct userinfo new_info ={
                    .id = loginfo.id,
                    .addr = clientAddr,
                };
                strcpy(new_info.username,loginfo.username);
                strcpy(new_info.password,loginfo.password);

                user_node *new = new_node(&new_info);
                if(new == NULL)
                    printf("new = NULL\n");
                add_user(all_users,new);
                strcpy(re_msg,"regist success");
                sendto(logfd, re_msg, strlen(re_msg), 0,
                (struct sockaddr *)&clientAddr, sizeof(clientAddr));
                printf("all_users:\n");
                show_list(all_users);
                sem_post(&usersInfoUpdate);
            }
        }
        
        //V操作，获取资源，
        sem_post(&usersUpdate);
    }
}


//线程3:消息聊天
void *chat(void *arg)
{
    pthread_detach(pthread_self());
    user_node *client = (user_node*)arg;

    //绑定当前登录的用户的套接字
    user_node *cur_client = find_out(cur_users,&client->user.addr);

    cur_client->user.connfd = client->user.connfd;
    cur_client->user.addr = client->user.addr;
    sem_post(&usersUpdate);
    user_node *aclient = find_user_by_id(all_users,cur_client->user.id);
    aclient->user.connfd = cur_client->user.connfd;
    aclient->user.addr = cur_client->user.addr;
    sem_post(&usersInfoUpdate);

    /**
     * int type;  //0:普通语句  1:文件
     * unsigned int id;//接收者的id
     * char msg[1024];
    */
    char *buf = calloc(1,USERDATA_MAXLEN);
    int n;

    // edit_client_friend(client->user.id);

    while(1)
    {
        bzero(buf, USERDATA_MAXLEN);
        n = read(client->user.connfd, buf, USERDATA_MAXLEN);

        if(n == -1)
        {
            perror("读取客户端消息失败");
            break;
        }

        if(n == 0)
        {
            printf("\r[%s:%hu]已下线\n", inet_ntoa(cur_client->user.addr.sin_addr), ntohs(cur_client->user.addr.sin_port));
            break;
        }
        
        printf("msg:%s\n",buf);
        char all_msg[USERDATA_MAXLEN];
        strcpy(all_msg,buf);
        //私发消息 type:id:message:size:filetext
        char *type = strtok(buf,":");

        char *id_s = strtok(NULL,":");
        char *msg = strtok(NULL,":");
        if(strcmp(type,"-1") == 0)
        {
            printf("获取服务端的好友数据\n");
            edit_client_friend(aclient->user.id);
        }
        else if(strcmp(type,"0") == 0) //普通消息
        {
            privateTalk(cur_client, 0,atoi(id_s), msg,0,all_msg);
        
        }else if(strcmp(type,"1") == 0)
        {
            char *size = strtok(NULL,":");
            int s = atoi(size);
            if(s > 0 )
            {
                char *filetext = strtok(NULL,":");
                privateTalk(cur_client,1, atoi(id_s), msg,s,filetext);
            }else{
                privateTalk(cur_client,1, atoi(id_s), msg,s,all_msg);
            }
        }else if(strcmp(type,"2") == 0) //申请好友的请求
        {
            // type:recvid:msg 2:7:zz:6:yes:0

            if(strcmp(msg,"yes") == 0)
            {
     
                user_node *node = find_user_by_id(all_users,atoi(id_s));
                //更新服务器的好友数据
                friend f1;
                f1.cur_id = cur_client->user.id;
                f1.id = atoi(id_s);
                strcpy(f1.username,node->user.username);
                frienddata[friendnum++] = f1;

                friend f2;
                f2.cur_id = atoi(id_s);
                f2.id = cur_client->user.id;
                strcpy(f2.username,cur_client->user.username);
                frienddata[friendnum++] = f2;
                printf("更新好友数据\n");
                save_friendinfo();
            }
            privateTalk(cur_client,2,atoi(id_s),msg,0,all_msg);
        }
    }
    close(cur_client->user.connfd);

    del_user(cur_client);
    free(cur_client);
    sem_post(&usersUpdate);
    sem_post(&usersInfoUpdate);
}

int main(int argc, char const *argv[])
{
    //1.创建TCP套接字
    msgfd = Socket(AF_INET,SOCK_STREAM,0); 
    logfd = Socket(AF_INET,SOCK_DGRAM,0);
    

    //2.设置地址重复使用
    int on = 1;
    Setsockopt(msgfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
    Setsockopt(logfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));

    //3.准备服务器地址结构体(IPV4)
    struct sockaddr_in addr;
    socklen_t len = sizeof(addr);
    bzero(&addr,len);
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);

    //4.绑定端口地址
    // 4.1:绑定登入登出端口
    addr.sin_port = htons(Login_Out_PORT);
    Bind(logfd,(struct sockaddr *)&addr,len);



    //4.2:绑定消息端口
    addr.sin_port = htons(MSG_PORT);
    Bind(msgfd,(struct sockaddr *)&addr, len);

    // 将sockfd设置为被动监听套接字
	listen(msgfd, 3);

    //5.准备化当前在线用户链表
    cur_users = init_list();
    sem_init(&usersUpdate,0,0);
    sem_init(&usersInfoUpdate,0,0);

    //5.线程0:动态更新用户库
    pthread_t tid0;
    pthread_create(&tid0,NULL,updatausers,NULL);

    //5.线程1:(动态更新)显示当前在线用户
    pthread_t tid1;
    pthread_create(&tid1,NULL,show_users,NULL);

    //线程2:用户登录或登出或注册
    pthread_t tid2;
    pthread_create(&tid2,NULL,login_logout,NULL);

    //主循环：每当来一个远程客户端，就分配一条专门的线程处理之
    while(1)
    {
        // 准备存储对端（客户端）的用户结构体
        user_node *newClient = calloc(1,sizeof(user_node));
        socklen_t len = sizeof(newClient->user.addr);

        if((newClient->user.connfd = accept(msgfd, (struct sockaddr *)&newClient->user.addr, &len)) < 0)
        {
            perror("accept失败");
            free(newClient);
            continue;
        }
        // printf("\r[%s:%hu]连接成功\n", inet_ntoa(newClient->user.addr.sin_addr), ntohs(newClient->user.addr.sin_port));

        //6.线程3:聊天
        pthread_t tid3;
        pthread_create(&tid3,NULL,chat,(void *)newClient);
    }
    return 0;
}


